package top.hcode.hoj.utils.crypt;

import org.nutz.lang.Strings;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public class EncryptionUtils {

    private final int[] abcde = {1732584193, -271733879, -1732584194, 271733878, -1009589776};
    private final int[] digestInt = new int[5];
    private final int[] tmpData = new int[80];

    private static final String appId = "EDBB000C470B237043653635FDFE2A84";

    private static final String appKey = "6B06766FD47983849A1BCEC9753418F0";

    private static String generateKeyInfo(String timestamp) {
        String keyInfo = appId + appKey + timestamp;
        byte[] hmacSHA = EncryptionUtils.getHmacSHA1(keyInfo, appKey);
        String digest = EncryptionUtils.bytesToHexString(hmacSHA);
        if (Strings.isNotBlank(digest)) {
            digest = digest.toUpperCase();
        }
        return digest;
    }

    public static String encrypt(String data, String secretKey, String type)
            throws Exception {
        if ("1".equals(type)) {
            data = AesEncryUtil.encrypt(data, secretKey);
        }
        if ("2".equals(type)) {
            data = DesEncryUtil.encrypt(data, secretKey);
        }
        return data;
    }

    public static String detrypt(String data, String secretKey, String type)
            throws Exception {
        if ("1".equals(type)) {
            data = AesEncryUtil.detrypt(data, secretKey);
        }
        if ("2".equals(type)) {
            data = DesEncryUtil.detrypt(data, secretKey);
        }
        return data;
    }

    public static byte[] getHmacSHA1(String data, String key) {
        byte[] ipadArray = new byte[64];
        byte[] opadArray = new byte[64];
        byte[] keyArray = new byte[64];
        int ex = key.length();
        EncryptionUtils sha1 = new EncryptionUtils();
        if (key.length() > 64) {
            byte[] temp = sha1.getDigestOfBytes(key.getBytes());
            ex = temp.length;
            System.arraycopy(temp, 0, keyArray, 0, ex);
        } else {
            byte[] temp = key.getBytes();
            System.arraycopy(temp, 0, keyArray, 0, temp.length);
        }
        for (int i = ex; i < 64; i++) {
            keyArray[i] = 0;
        }
        for (int j = 0; j < 64; j++) {
            ipadArray[j] = ((byte) (keyArray[j] ^ 0x36));
            opadArray[j] = ((byte) (keyArray[j] ^ 0x5C));
        }
        byte[] tempResult = sha1.getDigestOfBytes(join(ipadArray, data.getBytes(StandardCharsets.UTF_8)));
        return sha1.getDigestOfBytes(join(opadArray, tempResult));
    }

    public static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder("");
        if ((src == null) || (src.length <= 0)) {
            return null;
        }
        for (int value : src) {
            int v1 = value / 16;
            int v2 = value % 16;
            int v = value & 0xFF;
            String hv = Integer.toHexString(v).toUpperCase();
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }

    public byte[] getDigestOfBytes(byte[] byteData) {
        process_input_bytes(byteData);

        byte[] digest = new byte[20];
        for (int i = 0; i < this.digestInt.length; i++) {
            intToByteArray(this.digestInt[i], digest, i * 4);
        }
        return digest;
    }

    private static byte[] join(byte[] b1, byte[] b2) {
        int length = b1.length + b2.length;
        byte[] newer = new byte[length];
        System.arraycopy(b1, 0, newer, 0, b1.length);
        System.arraycopy(b2, 0, newer, b1.length, b2.length);
        return newer;
    }

    private int process_input_bytes(byte[] bytedata) {
        System.arraycopy(this.abcde, 0, this.digestInt, 0, this.abcde.length);

        byte[] newbyte = byteArrayFormatData(bytedata);

        int MCount = newbyte.length / 64;
        for (int pos = 0; pos < MCount; pos++) {
            for (int j = 0; j < 16; j++) {
                this.tmpData[j] = byteArrayToInt(newbyte, pos * 64 + j * 4);
            }
            encrypt();
        }
        return 20;
    }

    private void intToByteArray(int intValue, byte[] byteData, int i) {
        byteData[i] = ((byte) (intValue >>> 24));
        byteData[(i + 1)] = ((byte) (intValue >>> 16));
        byteData[(i + 2)] = ((byte) (intValue >>> 8));
        byteData[(i + 3)] = ((byte) intValue);
    }

    private byte[] byteArrayFormatData(byte[] bytedata) {
        int zeros;

        int size;

        int n = bytedata.length;

        int m = n % 64;
        if (m < 56) {
            zeros = 55 - m;
            size = n - m + 64;
        } else if (m == 56) {
            zeros = 63;
            size = n + 8 + 64;
        } else {
            zeros = 63 - m + 56;
            size = n + 64 - m + 64;
        }
        byte[] newbyte = new byte[size];

        System.arraycopy(bytedata, 0, newbyte, 0, n);

        int l = n;

        newbyte[(l++)] = Byte.MIN_VALUE;
        for (int i = 0; i < zeros; i++) {
            newbyte[(l++)] = 0;
        }
        long N = n * 8L;
        byte h8 = (byte) (int) (N & 0xFF);
        byte h7 = (byte) (int) (N >> 8 & 0xFF);
        byte h6 = (byte) (int) (N >> 16 & 0xFF);
        byte h5 = (byte) (int) (N >> 24 & 0xFF);
        byte h4 = (byte) (int) (N >> 32 & 0xFF);
        byte h3 = (byte) (int) (N >> 40 & 0xFF);
        byte h2 = (byte) (int) (N >> 48 & 0xFF);
        byte h1 = (byte) (int) (N >> 56);
        newbyte[(l++)] = h1;
        newbyte[(l++)] = h2;
        newbyte[(l++)] = h3;
        newbyte[(l++)] = h4;
        newbyte[(l++)] = h5;
        newbyte[(l++)] = h6;
        newbyte[(l++)] = h7;
        newbyte[(l)] = h8;

        return newbyte;
    }

    private int byteArrayToInt(byte[] bytedata, int i) {
        return (bytedata[i] & 0xFF) << 24 | (bytedata[(i + 1)] & 0xFF) << 16 | (bytedata[(i + 2)] & 0xFF) << 8 |
                bytedata[(i + 3)] & 0xFF;
    }

    private void encrypt() {
        for (int i = 16; i <= 79; i++) {
            this.tmpData[i] = f4(
                    this.tmpData[(i - 3)] ^ this.tmpData[(i - 8)] ^ this.tmpData[(i - 14)] ^ this.tmpData[(i - 16)], 1);
        }
        int[] tmpabcde = new int[5];
        System.arraycopy(this.digestInt, 0, tmpabcde, 0, tmpabcde.length);
        for (int j = 0; j <= 19; j++) {
            int tmp = f4(tmpabcde[0], 5) + f1(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + this.tmpData[j] +
                    1518500249;
            tmpabcde[4] = tmpabcde[3];
            tmpabcde[3] = tmpabcde[2];
            tmpabcde[2] = f4(tmpabcde[1], 30);
            tmpabcde[1] = tmpabcde[0];
            tmpabcde[0] = tmp;
        }
        for (int k = 20; k <= 39; k++) {
            int tmp = f4(tmpabcde[0], 5) + f2(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + this.tmpData[k] +
                    1859775393;
            tmpabcde[4] = tmpabcde[3];
            tmpabcde[3] = tmpabcde[2];
            tmpabcde[2] = f4(tmpabcde[1], 30);
            tmpabcde[1] = tmpabcde[0];
            tmpabcde[0] = tmp;
        }
        for (int l = 40; l <= 59; l++) {
            int tmp = f4(tmpabcde[0], 5) + f3(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + this.tmpData[l] +
                    -1894007588;
            tmpabcde[4] = tmpabcde[3];
            tmpabcde[3] = tmpabcde[2];
            tmpabcde[2] = f4(tmpabcde[1], 30);
            tmpabcde[1] = tmpabcde[0];
            tmpabcde[0] = tmp;
        }
        for (int m = 60; m <= 79; m++) {
            int tmp = f4(tmpabcde[0], 5) + f2(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + this.tmpData[m] +
                    -899497514;
            tmpabcde[4] = tmpabcde[3];
            tmpabcde[3] = tmpabcde[2];
            tmpabcde[2] = f4(tmpabcde[1], 30);
            tmpabcde[1] = tmpabcde[0];
            tmpabcde[0] = tmp;
        }
        for (int i2 = 0; i2 < tmpabcde.length; i2++) {
            this.digestInt[i2] += tmpabcde[i2];
        }
        Arrays.fill(this.tmpData, 0);
    }

    private int f1(int x, int y, int z) {
        return x & y | (~x) & z;
    }

    private int f2(int x, int y, int z) {
        return x ^ y ^ z;
    }

    private int f3(int x, int y, int z) {
        return x & y | x & z | y & z;
    }

    private int f4(int x, int y) {
        return x << y | x >>> 32 - y;
    }
}
